#pragma once
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <iostream>
#include <memory>
#include "logger.hpp"
namespace wsll
{
    bool Serialize(const Json::Value &root, std::string &out)
    {
        Json::StreamWriterBuilder writer; // 这是一个工厂类
        writer.settings_["emitUTF8"] = true;
        std::unique_ptr<Json::StreamWriter> jsonWriter(writer.newStreamWriter());
        std::stringstream ss;
        int ret = jsonWriter->write(root, &ss);
        if (ret != 0)
        {
            LOG_ERROR("Json::StreamWriter::write() failed");
            // std::cout<<"Json::StreamWriter::write() failed"<<std::endl;
            return false;
        }
        out = ss.str();
        return true;
    }
    bool UnSerialize(const std::string &in, Json::Value &root)
    {
        Json::CharReaderBuilder reader;
        std::unique_ptr<Json::CharReader> jsonReader(reader.newCharReader());
        std::string errs;
        bool ret = jsonReader->parse(in.c_str(), in.c_str() + in.size(), &root, &errs);
        if (!ret || !errs.empty())
        {
            LOG_ERROR("Json::CharReader::parse() failed,{}", errs);
            // std::cout<<"Json::CharReader::parse() failed"<<std::endl;
            return false;
        }
        return true;
    }
    class ESIndex
    {
    public:
        ESIndex(std::shared_ptr<elasticlient::Client> &client, const std::string &name, const std::string &type = "_doc")
            : _name(name), _type(type), _client(client)
        {
            Json::Value settings;
            Json::Value analysis;
            Json::Value analyzer;
            Json::Value ik;
            Json::Value tokenizer;
            tokenizer["tokenizer"] = "ik_max_word";
            ik["ik"] = tokenizer;
            analyzer["analyzer"] = ik;
            analysis["analysis"] = analyzer;
            _index["settings"] = analysis;

            std::string str;
            Serialize(settings, str);
            std::cout << str << std::endl;
        }
        ESIndex &append(const std::string &key, const std::string &type = "text", const std::string &analyzer = "ik_max_word", bool enabled = true) // 第三个是分词器选择
        {
            Json::Value field;
            field["type"] = type;
            field["analyzer"] = analyzer;
            if (enabled == false)
                field["enabled"] = enabled; // 因为默认是true，因此只需要false时才修改
            _properties[key] = field;
            return *this;
        }
        bool create(const std::string &index_id = "default_index_id")
        {
            Json::Value mappings;
            mappings["dynamic"] = true;
            mappings["properties"] = _properties;
            _index["mappings"] = mappings;

            std::string body;
            bool ret = Serialize(_index, body);
            if (!ret)
            {
                LOG_ERROR("序列化失败");
                return false;
            }
            // LOG_DEBUG("序列化成功,请求正文[{}]", body);
            //  发起请求
            try
            {
                auto rsp = _client->index(_name, _type, index_id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("创建ES索引{}失败,响应状态码异常{}", _name, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("创建ES索引{}失败,{}", _name, e.what());
                return false;
            }
            LOG_DEBUG("创建ES索引{}成功", _name);
            return true;
        }

    private:
        std::string _name;
        std::string _type;
        Json::Value _properties;
        Json::Value _index;
        std::shared_ptr<elasticlient::Client> _client;
    };
    class ESInsert
    {
    public:
        ESInsert(std::shared_ptr<elasticlient::Client> &client, const std::string &name, const std::string &type = "_doc")
            : _name(name), _type(type), _client(client) {}
        template <typename T>
        ESInsert &append(const std::string &key, const T &val) // 可能出现string long
        {
            _item[key] = val;
            return *this;
        }
        bool insert(const std::string _id = "")
        {
            std::string body;
            bool ret = Serialize(_item, body);
            if (!ret)
            {
                LOG_ERROR("序列化失败");
                return false;
            }
            // LOG_DEBUG("序列化成功,请求正文[{}]", body);
            //  发起请求
            try
            {
                auto rsp = _client->index(_name, _type, _id, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("插入ES数据{}失败,响应状态码异常{}", body, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("插入ES数据{}失败,{}", body, e.what());
                return false;
            }
            // LOG_DEBUG("插入ES数据{}成功", body);
            return true;
        }

    private:
        std::string _name;
        std::string _type;
        Json::Value _item;
        std::shared_ptr<elasticlient::Client> _client;
    };
    class ESRemove
    {
    public:
        ESRemove(std::shared_ptr<elasticlient::Client> &client, const std::string &name, const std::string &type = "_doc")
            : _name(name), _type(type), _client(client) {}
        bool remove(const std::string &_id)
        {
            try
            {
                auto rsp = _client->remove(_name, _type, _id);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("删除ES数据{}失败,响应状态码异常{}", _id, rsp.status_code);
                    return false;
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("删除ES数据{}失败,{}", _id, e.what());
                return false;
            }
            LOG_DEBUG("删除ES数据{}成功", _id);
            return true;
        }

    private:
        std::string _name;
        std::string _type;
        std::shared_ptr<elasticlient::Client> _client;
    };
    class ESSearch
    {
    public:
        ESSearch(std::shared_ptr<elasticlient::Client> &client, const std::string &name, const std::string &type = "_doc")
            : _name(name), _type(type), _client(client) {}
        ESSearch &append_must_not_terms(const std::string &key, const std::vector<std::string> &vals) // term:精确匹配，也就是得一模一样
        {
            Json::Value field;
            for (const auto &val : vals)
            {
                field[key].append(val);
            }
            Json::Value terms;
            terms["terms"] = field;
            _must_not.append(terms);
            return *this;
        }
        ESSearch &append_should_match(const std::string &key, const std::string &value) // match:模糊匹配，实际上是分词后的结果
        {
            Json::Value field;
            field[key] = value;
            Json::Value match;
            match["match"] = field;
            _should.append(match);
            return *this;
        }
        ESSearch &append_must_term(const std::string &key, const std::string &val) // must：所有的must项取交集 should：所有项取并集，也就是满足其一即可
        {
            Json::Value field;
            field[key] = val;
            Json::Value term;
            term["term"] = field;
            _must.append(term);
            return *this;
        }
        ESSearch &append_must_match(const std::string &key, const std::string &val)
        {
            Json::Value field;
            field[key] = val;
            Json::Value match;
            match["match"] = field;
            _must.append(match);
            return *this;
        }
        Json::Value search()
        {
            Json::Value cond;
            if (_must_not.empty() == false)
                cond["must_not"] = _must_not;
            if (_should.empty() == false)
                cond["should"] = _should;
            if (_must.empty() == false)
                cond["must"] = _must;
            Json::Value query;
            query["bool"] = cond;

            Json::Value root;
            root["query"] = query;
            std::string body;
            bool ret = Serialize(root, body);
            if (!ret)
            {
                LOG_ERROR("序列化失败");
                return Json::Value();
            }
            // LOG_DEBUG("序列化成功,请求正文[{}]", body);
            // 发起请求
            cpr::Response rsp;
            try
            {
                rsp = _client->search(_name, _type, body);
                if (rsp.status_code < 200 || rsp.status_code >= 300)
                {
                    LOG_ERROR("搜索ES数据{}失败,响应状态码异常{}", body, rsp.status_code);
                    return Json::Value();
                }
            }
            catch (const std::exception &e)
            {
                LOG_ERROR("搜索ES数据{}失败,{}", body, e.what());
                return Json::Value();
            }
            // 反序列化
            // LOG_DEBUG("接收ES数据{}成功", body);
            Json::Value _rsp;
            ret = UnSerialize(rsp.text, _rsp);
            if (!ret)
            {
                LOG_ERROR("检索结果{}反序列化失败", rsp.text);
                return Json::Value();
            }
            return _rsp["hits"]["hits"];
        }

    private:
        std::string _name;
        std::string _type;
        Json::Value _must_not;
        Json::Value _should;
        Json::Value _must;
        std::shared_ptr<elasticlient::Client> _client;
    };
}